<template>
  <el-dialog
    :visible.sync="isShow"
    title="经纬度坐标拾取"
    :close-on-click-modal="false"
    width="80%"
  >
    <el-form inline>
      <!-- 放置标签的 -->
      <slot v-if="$scopedSlots.label" name="label" :data="formModel"></slot>
      <template v-if="!$scopedSlots.label">
        <el-form-item label="坐标">
          <el-input
            style="width: 400px"
            disabled
            v-model="coordinate"
          ></el-input>
        </el-form-item>
        <el-form-item label="地址">
          <el-input style="width: 300px" disabled v-model="address"></el-input>
        </el-form-item>
      </template>
      <el-form-item>
        <el-button
          :disabled="isDisableSubmit"
          type="primary"
          @click="closeModel(true)"
          >确 认</el-button
        >
        <el-button @click="closeModel">取 消</el-button>
      </el-form-item>
    </el-form>

    <!-- 地图区域 -->
    <div class="map-pick-area">
      <div class="search-container" v-show="mapLoaded">
        <el-input
          v-model="keyword"
          type="text"
          id="place"
          placeholder="请输入地点"
          @change="handleSearch"
        >
          <i
            class="el-icon-search el-input__icon"
            slot="suffix"
            @click="handleSearch"
          >
          </i>
        </el-input>
      </div>
      <div
        @click="changeViewMode"
        class="change-btn"
        :class="{ active: baseMap === 'satellite' }"
        v-show="mapLoaded"
      >
        卫星
      </div>
      <div id="map-container"></div>
    </div>
  </el-dialog>
</template>

<script>
import Vue from 'vue';
import { VueJsonp } from 'vue-jsonp';
import { cutPostion, getMapBounds } from './utils';
import { bindEventListener, removeEventListener } from './events';
import registerPlugins from './registerPlugins';
import activeImg from './assets/active-point.png';

Vue.use(VueJsonp);

function loadScript(key, id = 'tengxun-map') {
  if (!key) {
    console.log(
      '[error] 请传入开发者key: https://lbs.qq.com/webApi/javascriptGL/glGuide/glBasic'
    );
    return;
  }

  let hasMapSDK = document.querySelector(`#${id}`);

  if (hasMapSDK) {
    // 存在sdk 直接执行初始化
    window.initMap();
    return;
  }

  let script = document.createElement('script');
  script.type = 'text/javascript';
  script.id = id;
  script.src = `https://map.qq.com/api/gljs?v=2.exp&key=${key}&callback=initMap&libraries=place&debug=false`;
  document.body.appendChild(script);
}

export default {
  name: 'MapPickPosition',
  props: {
    /** 开发者key 可在 控制台 -> 应用管理 -> 我的应用 界面进行创建得到 https://lbs.qq.com/webApi/javascriptGL/glGuide/glBasic */
    appKey: {
      type: String,
      require: true,
    },
    isShowMapModal: {
      type: Boolean,
      default: false,
    },
    hasLoading: {
      type: Boolean,
      default: true,
    },
    /** 鼠标移动实时显示经纬度 */
    mousemovePosition: {
      type: Boolean,
      default: false,
    },
    /** 点击时有 poi信息才展示， 默认随便哪里都可以点 */
    limitPOI: {
      type: Boolean,
      default: false,
    },
    /**
     * 默认外部传入的经纬度，作为初始化中心
     */
    defaultPosition: {
      type: Object,
      validator(map) {
        if (!map || typeof map !== 'object') {
          console.log('默认position格式： {lat: 2, lng: 12.22}');
          return false;
        }

        if (!map.lat || !map.lng) {
          console.warn(
            `默认position格式： {lat: 2, lng: 12.22}, 当前传--> ${JSON.stringify(
              map
            )}`
          );
        }

        return true;
      },
    },
  },
  data() {
    return {
      /** 搜索关键字 */
      keyword: '',
      formModel: {
        /** 下面的属性都是腾讯接口返回 */
        /** 纬度 */
        lat: '',
        /** 经度 */
        lng: '',
        /** 省 */
        province: '',
        /** 市 */
        city: '',
        /** 区 */
        district: '',
        /** 具体位置 */
        addr: '',
        /** 行政区ID */
        adcode: '',
      },
      /** 地图实例 */
      map: null,
      /** 标记物 */
      markerLayer: null,
      /** 文本标记 */
      multiLabel: null,
      /** 展示信息弹窗的实例对象 */
      infoWindow: null,
      /** 通过搜索匹配到的标记物 */
      searchMarkerList: [],
      // /** 点击事件返回的事件监听对象 */
      // clickListenerHandler: null,
      // showMarkerMessageHandler: null,
      /** 默认地图类型 - 矢量图; satellite - 卫星图 */
      baseMap: 'vector',
      /** 地图是否加载完成 */
      mapLoaded: false,

      /** 最大获取当前位置调用次数 */
      getPositionMaxTimes: 3,
      loading: null,
      /** 地图区域范围 */
      mapBounds: {
        /** 左上角 东北角 */
        ne: null,
        /** 右下角 西南角 */
        sw: null,
      },
    };
  },
  computed: {
    isShow: {
      get() {
        return this.isShowMapModal;
      },
      set() {
        this.$emit('update:isShowMapModal', false);
      },
    },
    isDisableSubmit() {
      return this.address === '';
    },
    coordinate() {
      if (!this.formModel.lat) return '';

      return (
        cutPostion(this.formModel.lat) + ',' + cutPostion(this.formModel.lng)
      );
    },
    address() {
      const { province, city, district, addr } = this.formModel;
      if (!province) return;

      return `${province}${city}${district}${addr}`;
    },
  },
  watch: {
    isShowMapModal(val) {
      if (val) {
        this.init();
      }
    },
  },
  created() {
    loadScript(this.appKey);
  },
  mounted() {
    if (!this.isShow) return;

    // 挂载异步回调
    window.initMap = this.init;
  },
  // 强烈建议父组件使用v-if 控制地图组件的显示隐藏
  beforeDestroy() {
    this.destory();
  },
  methods: {
    async init() {
      if (this.hasLoading) {
        this.$nextTick(() => {
          this.loading = this.$loading({
            target: document.querySelector('.map-pick-area'),
            fullscreen: false,
            text: '地图初始化中...',
          });
        });
      }

      // 使用默认外部传入的地址做初始化
      if (
        this.defaultPosition &&
        this.defaultPosition.lat &&
        this.defaultPosition.lng
      ) {
        const { lat, lng } = this.defaultPosition;
        this.formModel.lng = lng;
        this.formModel.lat = lat;

        try {
          const res = await this.trasnformPosition(lat, lng);
          this.formModel.adcode = res.adcode;
          this.formModel.addr = res.addr;
          this.formModel.city = res.city;
          this.formModel.district = res.district;
          this.formModel.province = res.province;

          this.initMap();
        } catch (e) {
          console.log(
            '使用默认地址反解析地址异常-->',
            e,
            '当前lat-->',
            this.formModel.lat
          );
          // 解析地址出现异常，直接先将地图初始化出来
          this.initMap();
        }

        return;
      }

      // https://blog.csdn.net/weixin_45615467/article/details/109672940
      // 1 获取用户位置信息
      this.getLocation();
    },

    closeModel(isSubmit) {
      if (isSubmit) {
        this.$emit('updatePosition', this.formModel);
      }
      this.$emit('update:isShowMapModal', false);
    },

    /**
     * 腾讯地图相关逻辑
     */
    async getLocation() {
      const url = 'https://apis.map.qq.com/ws/location/v1/ip';
      const params = {
        key: this.appKey,
        output: 'jsonp',
      };

      try {
        const { status, message, result } = await this.$jsonp(url, params);

        if (status === 110) {
          console.log('应用没授权', message);
          return;
        }

        const postion = {
          ...result.ad_info,
          ...result.location,
        };

        this.getLocationSucess(postion);
      } catch (e) {
        console.log('请求异常');
        this.getLocationError(e);
      }

      console.log(
        '地理位置信息获取中, 地图白屏, 可能是因为位置获取接口缓慢导致...'
      );
    },

    /**
     * 获取定位成功的回调
     */
    getLocationSucess(position) {
      this.formModel = Object.assign(this.formModel, { ...position });

      this.initMap();
    },

    /**
     * 获取定位失败的回调
     */
    getLocationError(err) {
      console.log('地图获取用户位置信息报错-->', err);
      if (this.getPositionMaxTimes === 0) {
        // 没拿到中心点坐标,直接初始化地图
        this.initMap();
        return;
      }

      this.getPositionMaxTimes--;

      // 可以再重新调用
      this.getLocation();
    },

    /**
     * 设置地图中心点
     */
    setMapCenter() {
      try {
        // api link: 经纬度转换成坐标对象
        const currentPosition = new TMap.LatLng(
          this.formModel.lat,
          this.formModel.lng
        );

        this.map.setCenter(currentPosition);
      } catch (e) {
        console.warn('setMapCenter--', e, '当前lat-->', this.formModel.lat);
      }
    },

    /**
     * 初始化地图
     */
    initMap() {
      // api link: https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#2
      const options = {
        // 地图缩放级别
        zoom: 20,
      };

      this.$nextTick(() => {
        const el = document.querySelector('#map-container');
        /** 1. 初始化地图, this.map 是地图实例对象 */
        this.map = new TMap.Map(el, options);

        this.loading && this.loading.close();

        // 初始化文本标注
        this.createTextLable();

        // 初始化点标记
        this.createMultiMarker();

        // 由于不同人的网络原因 - 不一定所有用户都能拿到初始化经纬度
        if (this.formModel.lat) {
          this.setMapCenter();

          this.setCurrentUserPosition();
        }

        // 2. 添加事件
        bindEventListener(this);

        // 添加控件
        registerPlugins(this);

        this.mapBounds = getMapBounds(this.map);

        this.mapLoaded = true;
      });
    },
    /**
     * 初始化当前用户的位置
     */
    setCurrentUserPosition() {
      try {
        const currentPosition = new TMap.LatLng(
          this.formModel.lat,
          this.formModel.lng
        );

        this.markerLayer.add([
          {
            position: currentPosition,
            // 必须传空对象,下面有对这个地方使用 Object.keys
            properties: {
              pointType: 'init',
              location: {
                lat: this.formModel.lat,
                lng: this.formModel.lng,
              },
              address: this.address,
              title: '',
            },
          },
        ]);

        new TMap.MultiLabel({
          map: this.map,
          styles: {
            label: new TMap.LabelStyle({
              color: '#FF0000',
              offset: { x: 0, y: 10 },
            }),
          },
          geometries: [
            {
              position: currentPosition,
              content: '您在这儿',
              styleId: 'label',
            },
          ],
        });
      } catch (e) {
        console.warn(
          'setCurrentUserPosition',
          e,
          '当前lat-->',
          this.formModel.lat
        );
      }
    },
    /**
     * 用于判断指定坐标是否在地图范围内
     * @parmas {Number} lat 纬度值
     * @params {Number} lng 经度值
     * @returns {Boolean}
     * @link https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocClass
     */
    isContainesPoint(lat, lng) {
      if (!lat || !lng || Number.isNaN(lat) || Number.isNaN(lng)) {
        console.log(
          `经纬度参数异常, lat -- >${lat}, lng --> ${lng} , 请传入非空的数字`
        );
        return;
      }

      const point = new TMap.LatLng(lat, lng);
      if (Object.keys(this.mapBounds).length === 2) {
        const latLngBounds = new TMap.LatLngBounds(
          this.mapBounds.sw,
          this.mapBounds.ne
        );

        return latLngBounds.contains(point);
      } else {
        console.log('没有拿到地图边界坐标信息');

        return false;
      }
    },
    /**
     * 创建信息窗口
     */
    createInfoWindow() {
      this.infoWindow = new TMap.InfoWindow({
        map: this.map,
        position: this.map.getCenter(),
        // 设置信息弹窗的偏移量，否则会和marker重合
        offset: {
          x: 0,
          y: -48,
        },
      }).close();
    },

    /**
     * 创建一个文本标记
     */
    createTextLable() {
      const LabelStyle = new TMap.LabelStyle({
        color: '#ff0000',
        size: 16,
        offset: {
          x: 100,
          y: 20,
        },
      });

      this.multiLabel = new TMap.MultiLabel({
        id: 'text-label',
        map: this.map,
        styles: {
          label: LabelStyle,
        },
        geometries: [],
      });
    },

    /**
     * 初始化点标记对象
     */
    createMultiMarker() {
      const markerStyle = new TMap.MarkerStyle({
        width: 40,
        height: 50,
        src: activeImg,
      });

      // 初始化标注
      this.markerLayer = new TMap.MultiMarker({
        map: this.map,
        geometries: [],
        styles: {
          blue: markerStyle,
        },
      });
    },
    /**
     * 切换模式
     */
    changeViewMode() {
      this.baseMap = this.baseMap === 'vector' ? 'satellite' : 'vector';
      this.map.setBaseMap({ type: this.baseMap });
    },

    /**
     * 在vue组件销毁, 移除事件监听,销毁 map对象
     */
    destory() {
      removeEventListener(this);

      this.resetModel();
    },

    /**
     * 针对定义的数据进行重置
     */
    resetModel() {
      // 重置所有跟位置相关的数据
      this.formModel = this.$options.data().formModel;

      this.mapLoaded = false;

      this.getPositionMaxTimes = this.$options.data().getPositionMaxTimes;
      this.keyword = '';
    },

    /**
     * 搜索地点
     */
    async handleSearch() {
      // 清空之前的搜索打点数据
      const searchListIds = this.markerLayer.getGeometries().map((item) => {
        if (item.properties && item.properties.pointType === 'search') {
          return item.id;
        }
      });
      this.markerLayer && this.markerLayer.remove(searchListIds);
      this.searchMarkerList = [];

      this.infoWindow && this.infoWindow.close();

      if (this.keyword === '' || this.keyword.trim() === '') return;

      // !注意 url 中文需要编码
      const keyword = encodeURI(this.keyword.trim());
      const url = 'https://apis.map.qq.com/ws/place/v1/search';
      const params = {
        keyword,
        boundary: `region(${encodeURI(this.formModel.city)})`,
        key: this.appKey,
        output: 'jsonp',
        page_size: 20,
      };
      const res = await this.$jsonp(url, params);
      const { status, data } = res;

      if (status === 0) {
        const pointGeometryList = [];
        const markerList = [];

        // 对地图添加标注
        data.forEach((item, index) => {
          pointGeometryList.push({
            position: new TMap.LatLng(item.location.lat, item.location.lng),
            properties: {
              title: item.title,
              address: item.address,
              location: item.location,
              uuid: index,
              pointType: 'search',
            },
          });

          markerList.push({
            ...item,
            uuid: index,
            pointType: 'search',
          });
        });

        this.searchMarkerList = markerList;
        this.markerLayer.add(pointGeometryList);

        // 自适应地图
        // api link https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocClass#2
        const bounds = new TMap.LatLngBounds();
        pointGeometryList.forEach((item) => {
          // 若坐标点不在范围内，扩大bounds范围
          if (bounds.isEmpty() || !bounds.contains(item.position)) {
            bounds.extend(item.position);
          }
        });

        // bounds 可能是 {}，用户搜索的结果是 []， bounds是一个坐标
        if (Object.keys(bounds).length !== 0) {
          // 设置地图可视范围
          this.map.fitBounds(bounds, {
            padding: 100, // 自适应边距
          });
        }
      }
    },

    /**
     * 将经纬度转换成坐标位置描述
     * @param {Number | String} lat 纬度
     * @param {Number | String} lng 经度
     */
    async trasnformPosition(lat, lng) {
      const url = 'https://apis.map.qq.com/ws/geocoder/v1/';
      const params = {
        location: lat + ',' + lng,
        key: this.appKey,
        output: 'jsonp',
      };

      try {
        const { status, result } = await this.$jsonp(url, params);

        return new Promise((resolve, reject) => {
          if (!result) reject(new Error('未解析到地址'));

          const { province, city, district, street } = result.address_component;
          // eslint 不允许下划线命名
          const streetNumber = result.address_component.street_number;
          const adcode = result.ad_info.adcode;

          if (status === 0) {
            resolve({
              adcode,
              lat,
              lng,
              province,
              city,
              district,
              addr: street + streetNumber,
              location: result.location,
              address: result.address,
            });
          } else {
            reject(new Error('地址转换失败'));
          }
        });
      } catch (e) {
        console.log(e);
      }
    },

    /**
     * 清除搜索关键字
     */
    cleanSearchKey() {
      this.keyword = '';
    },
  },
};
</script>

<style lang="less" scoped>
@import './assets/index.less';
</style>
